package searchtab.views;

import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.swt.SWT;
import org.eclipse.swt.accessibility.ACC;
import org.eclipse.swt.accessibility.AccessibleAdapter;
import org.eclipse.swt.accessibility.AccessibleControlAdapter;
import org.eclipse.swt.accessibility.AccessibleControlEvent;
import org.eclipse.swt.accessibility.AccessibleEvent;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.FocusAdapter;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseMoveListener;
import org.eclipse.swt.events.MouseTrackListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.FontMetrics;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.IActionBars;

import searchtab.SearchTabActivator;
import searchtab.preferences.Preference;
import searchtab.preferences.PreferencesManager;
import searchtab.search.options.SearchOptions;
import searchtab.search.textfile.ISearcherEventListener;
import searchtab.search.textfile.SearcherEvent;
import searchtab.util.SearchTabUtils;

/**
 * A simple control that provides a text widget and a tree viewer. The contents of the text widget are used to
 * drive a SearchPatternFilter that is on the viewer.
 * 
 * @since 3.2
 */
public class SearchTab extends Composite implements ISearcherEventListener, IPropertyChangeListener {

    /**
     * The filter text widget to be used by this tree. This value may be <code>null</code> if there is no
     * filter widget, or if the controls have not yet been created.
     */
    protected Text filterText;

    /**
     * The Composite on which the filter controls are created. This is used to set the background color of the
     * filter controls to match the surrounding controls.
     */
    protected Composite filterComposite;

    /**
     * The pattern filter for the tree. This value must not be <code>null</code>.
     */

    /**
     * The text to initially show in the filter text control.
     */
    protected String initialText = "";

    /**
     * The parent composite of the filtered tree.
     * 
     * @since 3.3
     */
    protected Composite parent;

    private boolean noResultState;

    private Color defaultFilterForeground;

    private final SearchTabView searchTabView;

    private Button nextMatchButton;

    private Button previousMatchButton;

    private Color defaultFilterBackground;

    private Color noResultsBackground;

    private Color noResultFilterForeground;

    private Label infoLabel;

    private Label infoImageLabel;

    private Button highllightAllButton;

    protected Control clearButtonControl;

    private Composite filterTextComposite;

    private Action closeOnEscAction;

    private Action switchFocusOnEscAction;

    private Button matchCaseButton;

    private Action highlightByDefaultAction;

    private Action matchCaseByDefaultAction;

    /**
     * Create a new instance of the receiver.
     * 
     * @param parent the parent <code>Composite</code>
     */
    public SearchTab(Composite parent, SearchTabView searchTabView) {
        super(parent, SWT.NONE);
        this.parent = parent;
        this.searchTabView = searchTabView;

        PreferencesManager.addListener(this);
        init();
    }

    /**
     * Create the filtered tree.
     * 
     * @param treeStyle the style bits for the <code>Tree</code>
     * @param filter the filter to be used
     * 
     * @since 3.3
     */
    protected void init() {
        createControl(parent);

        setFont(parent.getFont());

        noResultsBackground = new Color(filterText.getShell().getDisplay(), 255, 128, 128);
        noResultFilterForeground = new Color(filterText.getShell().getDisplay(), 255, 255, 255);

        setControlsEnabled(false);

        SearchTabActivator.getDefault().getSearcherEngine().getCurrentSeacher().addListener(this);

    }

    /**
     * Create the filtered tree's controls. Subclasses should override.
     * 
     * @param parent
     * @param treeStyle
     */
    protected void createControl(Composite parent) {
        GridLayout layout = new GridLayout();
        layout.marginHeight = 0;
        layout.marginWidth = 0;
        setLayout(layout);
        setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));

        filterComposite = new Composite(this, SWT.NONE);
        GridLayout filterLayout = new GridLayout(10, false);
        filterLayout.marginHeight = 0;
        filterLayout.marginWidth = 0;
        filterComposite.setLayout(filterLayout);
        filterComposite.setFont(parent.getFont());

        Image closeImage = SearchTabActivator.getImageDescriptor("icons/close_cross.gif").createImage();
        Button closeLabel = new Button(filterComposite, SWT.PUSH);
        closeLabel.setImage(closeImage);
        closeLabel.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                SearchTabUtils.getWorkbenchWindow().hideView(searchTabView);
            }
        });
        closeLabel.setLayoutData(new GridData(20, 20));
        Label label = new Label(filterComposite, SWT.NONE);
        label.setText(Messages.SearchTab_FindLabel);

        createFilterControls(filterComposite);

        clearButtonControl.setVisible(false);

        Image nextImage = SearchTabActivator.getImageDescriptor("icons/search_next.gif").createImage();

        Image prevImage = SearchTabActivator.getImageDescriptor("icons/search_prev.gif").createImage();

        nextMatchButton = new Button(filterComposite, SWT.PUSH | SWT.NO_FOCUS);
        nextMatchButton.setImage(nextImage);
        nextMatchButton.setText(Messages.SearchTab_NextButton);
        nextMatchButton.addMouseListener(new MouseAdapter() {

            @Override
            public void mouseDown(MouseEvent e) {
                SearchTabActivator.getDefault().getSearcherEngine().goToNextMatch();
            }
        }

        );

        previousMatchButton = new Button(filterComposite, SWT.PUSH | SWT.NO_FOCUS);
        previousMatchButton.setImage(prevImage);
        previousMatchButton.setText(Messages.SearchTab_PreviuosButton);
        previousMatchButton.addMouseListener(new MouseAdapter() {

            @Override
            public void mouseDown(MouseEvent e) {
                SearchTabActivator.getDefault().getSearcherEngine().goToPreviousMatch();
            }
        }

        );

        highllightAllButton = new Button(filterComposite, SWT.CHECK | SWT.NO_FOCUS);

        highllightAllButton.setText(Messages.SearchTab_HighlightAll);
        highllightAllButton.setSelection(PreferencesManager.getBoolean(Preference.DEFAULT_HIGHLIGHT));
        SearchOptions.setHighlightAll(highllightAllButton.getSelection());        
        highllightAllButton.addSelectionListener(new SelectionListener() {

            public void widgetSelected(SelectionEvent arg0) {
                SearchOptions.setHighlightAll(highllightAllButton.getSelection());
                if (highllightAllButton.getSelection()) {
                    SearchTabActivator.getDefault().getSearcherEngine().markResults();
                } else {
                    SearchTabActivator.getDefault().getSearcherEngine().unmarkResults();
                }
            }

            public void widgetDefaultSelected(SelectionEvent arg0) {
            }
        });

        matchCaseButton = new Button(filterComposite, SWT.CHECK | SWT.NO_FOCUS);
        matchCaseButton.setText(Messages.SearchTab_MatchCase);
        matchCaseButton.setSelection(PreferencesManager.getBoolean(Preference.DEFAULT_MATCH_CASE));
        SearchOptions.setMatchCase(matchCaseButton.getSelection());        
        matchCaseButton.addSelectionListener(new SelectionAdapter() {

            @Override
            public void widgetSelected(SelectionEvent arg0) {
                SearchOptions.setMatchCase(matchCaseButton.getSelection());
                SearchTabActivator.getDefault().getSearcherEngine().changeMatchCaseOption();
            }

        });

        Image image = parent.getDisplay().getSystemImage(SWT.ICON_INFORMATION);
        infoImageLabel = new Label(filterComposite, SWT.HORIZONTAL);
        infoImageLabel.setImage(image);
        infoImageLabel.setVisible(false);

        infoLabel = new Label(filterComposite, SWT.HORIZONTAL);
        infoLabel.setText(Messages.SearchTab_PhraseNotFound);
        infoLabel.setVisible(false);
        GridData gridData = new GridData(GridData.FILL_HORIZONTAL);
        gridData.horizontalIndent = 8;
        gridData.horizontalSpan = 2;
        gridData.widthHint = 200;
        infoLabel.setLayoutData(gridData);

        GC gc = new GC(filterText);
        FontMetrics fm = gc.getFontMetrics();
        int column = 40;
        int width = column * fm.getAverageCharWidth();
        int height = fm.getHeight();
        gc.dispose();
        filterText.setLayoutData(new GridData(width, height));

        final IActionBars bars = searchTabView.getViewSite().getActionBars();
        final IMenuManager mmanager = bars.getMenuManager();        
        closeOnEscAction = new Action("Close search tab on ESC", IAction.AS_RADIO_BUTTON) {            
            @Override
            public void run() {
                PreferencesManager.setPreference(Preference.CLOSE_ON_ESC, String.valueOf(true));
            }
        };
        closeOnEscAction.setChecked(PreferencesManager.getBoolean(Preference.CLOSE_ON_ESC));
        mmanager.add(closeOnEscAction);
        switchFocusOnEscAction = new Action("Switch focus on ESC", IAction.AS_RADIO_BUTTON) {
            @Override
            public void run() {
                PreferencesManager.setPreference(Preference.CLOSE_ON_ESC, String.valueOf(false));
            }
        };
        switchFocusOnEscAction.setChecked(!PreferencesManager.getBoolean(Preference.CLOSE_ON_ESC));
        mmanager.add(switchFocusOnEscAction);
        mmanager.add(new Separator());
        highlightByDefaultAction = new Action("Highlight All by default", IAction.AS_CHECK_BOX) {
            @Override
            public void run() {
                PreferencesManager.setPreference(Preference.DEFAULT_HIGHLIGHT, String.valueOf(isChecked()));
            }
        };
        highlightByDefaultAction.setChecked(PreferencesManager.getBoolean(Preference.DEFAULT_HIGHLIGHT));
        mmanager.add(highlightByDefaultAction);
        matchCaseByDefaultAction = new Action("Match Case by default", IAction.AS_CHECK_BOX) {
            @Override
            public void run() {
                PreferencesManager.setPreference(Preference.DEFAULT_MATCH_CASE, String.valueOf(isChecked()));
            }
        };
        matchCaseByDefaultAction.setChecked(PreferencesManager.getBoolean(Preference.DEFAULT_MATCH_CASE));
        mmanager.add(matchCaseByDefaultAction);        

    }

    private void updateCloseOnESCMenuState() {
        closeOnEscAction.setChecked(PreferencesManager.getBoolean(Preference.CLOSE_ON_ESC));
        switchFocusOnEscAction.setChecked(!PreferencesManager.getBoolean(Preference.CLOSE_ON_ESC));
        highlightByDefaultAction.setChecked(PreferencesManager.getBoolean(Preference.DEFAULT_HIGHLIGHT));
        matchCaseByDefaultAction.setChecked(PreferencesManager.getBoolean(Preference.DEFAULT_MATCH_CASE));
    }

    /**
     * Create the filter controls. By default, a text and corresponding tool bar button that clears the
     * contents of the text is created. Subclasses may override.
     * 
     * @param parent parent <code>Composite</code> of the filter controls
     * @return the <code>Composite</code> that contains the filter controls
     */
    protected Composite createFilterControls(Composite parent) {
        filterTextComposite = new Composite(parent, SWT.BORDER);
        GridLayout filterLayout = new GridLayout(2, false);
        filterLayout.marginHeight = 0;
        filterLayout.marginWidth = 0;
        filterTextComposite.setLayout(filterLayout);
        filterTextComposite.setFont(parent.getFont());
        filterTextComposite.setBackground(parent.getDisplay().getSystemColor(SWT.COLOR_LIST_BACKGROUND));

        createTextField(filterTextComposite);

        return parent;
    }

    protected void createTextField(Composite parent) {
        filterText = doCreateTextField(parent);
        createClearTextNew(parent);
        defaultFilterBackground = filterText.getBackground();
        defaultFilterForeground = filterText.getForeground();
        filterText.getAccessible().addAccessibleListener(new AccessibleAdapter() {

            @Override
            public void getName(AccessibleEvent e) {
                e.result = initialText;
            }

        });

        filterText.addFocusListener(new FocusAdapter() {

            @Override
            public void focusGained(FocusEvent e) {
                /*
                 * Running in an asyncExec because the selectAll() does not appear to work when using mouse to
                 * give focus to text.
                 */
                Display display = filterText.getDisplay();
                display.asyncExec(new Runnable() {
                    public void run() {
                        if (!filterText.isDisposed()) {

                            filterText.selectAll();

                        }
                    }
                });
                return;
            }

        });

    }

    protected Text doCreateTextField(Composite parent) {

        Text text = new Text(parent, SWT.SINGLE);
        GridData gridData = new GridData(GridData.FILL_HORIZONTAL);
        gridData.horizontalIndent = 8;
        gridData.horizontalSpan = 2;
        gridData.widthHint = 300;
        text.setLayoutData(gridData);
        text.addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                switch (e.keyCode) {
                case SWT.ESC:
                    if (PreferencesManager.getBoolean(Preference.CLOSE_ON_ESC)) {
                        SearchTabUtils.getWorkbenchWindow().hideView(searchTabView);
                    } else {
                        SearchTabUtils.getWorkbenchWindow().getActiveEditor().setFocus();
                    }
                    break;
                case SWT.CR:
                    SearchTabActivator.getDefault().getSearcherEngine().goToNextMatch();
                    break;
                }
            }
        });
        return text;
    }

    public void setNoResultsState() {
        if (!filterText.isDisposed()) {
            defaultFilterBackground = filterText.getBackground();
            defaultFilterForeground = filterText.getForeground();

            filterText.setBackground(noResultsBackground);
            filterText.setForeground(noResultFilterForeground);

            filterTextComposite.setBackground(noResultsBackground);
            filterTextComposite.setForeground(noResultFilterForeground);
            clearButtonControl.setBackground(noResultsBackground);
        }
        noResultState = true;

        if (!infoLabel.isDisposed()) {
            infoLabel.setVisible(true);
        }
        if (!infoImageLabel.isDisposed()) {
            infoImageLabel.setVisible(true);
            infoLabel.setText(Messages.SearchTab_PhraseNotFound);
        }

    }

    public void setFoundResultsState(int resultsNumber) {
        if (!filterText.isDisposed()) {
            filterText.setBackground(defaultFilterBackground);
            filterText.setForeground(defaultFilterForeground);

            filterTextComposite.setBackground(defaultFilterBackground);
            filterTextComposite.setForeground(defaultFilterForeground);
            clearButtonControl.setBackground(defaultFilterBackground);
        }
        noResultState = false;
        // if (!infoLabel.isDisposed()) {
        // infoLabel.setVisible(false);
        // }
        infoLabel.setVisible(true);
        infoLabel.setText(resultsNumber + " matches");
        if (!infoImageLabel.isDisposed()) {
            infoImageLabel.setVisible(false);
        }
    }

    public void setEmtyFilterState() {
        if (!filterText.isDisposed()) {
            filterText.setBackground(defaultFilterBackground);
            filterText.setForeground(defaultFilterForeground);

            filterTextComposite.setBackground(defaultFilterBackground);
            filterTextComposite.setForeground(defaultFilterForeground);
            clearButtonControl.setBackground(defaultFilterBackground);
        }
        noResultState = false;
        if (!infoLabel.isDisposed()) {
            infoLabel.setVisible(false);
        }
        if (!infoImageLabel.isDisposed()) {
            infoImageLabel.setVisible(false);
        }
    }

    /**
     * Update the receiver after the text has changed.
     */
    protected void textChanged() {
    }

    /**
     * Set the background for the widgets that support the filter text area.
     * 
     * @param background background <code>Color</code> to set
     */
    @Override
    public void setBackground(Color background) {
        super.setBackground(background);
        if (filterComposite != null) {
            filterComposite.setBackground(background);
        }

    }

    /**
     * Set the text in the filter control.
     * 
     * @param string
     */
    protected void setFilterText(String string) {
        if (filterText != null) {
            filterText.setText(string);
            selectAll();
        }
    }

    /**
     * Get the filter text for the receiver, if it was created. Otherwise return <code>null</code>.
     * 
     * @return the filter Text, or null if it was not created
     */
    public Text getFilterControl() {
        return filterText;
    }

    /**
     * Convenience method to return the text of the filter control. If the text widget is not created, then
     * null is returned.
     * 
     * @return String in the text, or null if the text does not exist
     */
    protected String getFilterString() {
        return filterText != null ? filterText.getText() : null;
    }

    /**
     * Select all text in the filter text field.
     * 
     */
    protected void selectAll() {
        if (filterText != null) {
            filterText.selectAll();
        }
    }

    /**
     * Get the initial text for the receiver.
     * 
     * @return String
     */
    protected String getInitialText() {
        return initialText;
    }

    public void onGetResultEvent(SearcherEvent resultEvent) {
        clearButtonControl.setVisible(filterText.getText().length() > 0);
        if (resultEvent.isFilterEmpty()) {
            setEmtyFilterState();
            setControlsEnabled(false);
            return;
        } else if (!noResultState && resultEvent.getResultNumber() == 0) {
            setNoResultsState();
        } else if (/* noResultState && */resultEvent.getResultNumber() != 0) {
            setFoundResultsState(resultEvent.getResultNumber());
        }
        setControlsEnabled(true);

    }

    private void setControlsEnabled(boolean isEnabled) {
        if (!previousMatchButton.isDisposed()) {
            previousMatchButton.setEnabled(isEnabled);
        }
        if (!nextMatchButton.isDisposed()) {
            nextMatchButton.setEnabled(isEnabled);
        }
        if (!highllightAllButton.isDisposed()) {
            highllightAllButton.setEnabled(isEnabled);
        }
    }

    /**
     * Create the button that clears the text.
     * 
     * @param parent parent <code>Composite</code> of toolbar button
     */
    private void createClearTextNew(Composite parent) {
        // only create the button if the text widget doesn't support one
        // natively
        if ((filterText.getStyle() & SWT.ICON_CANCEL) == 0) {
            final Image inactiveImage = SearchTabActivator.getImageDescriptor("icons/clear_co_dis.gif")
                    .createImage();
            final Image activeImage = SearchTabActivator.getImageDescriptor("icons/clear_co_en.gif")
                    .createImage();
            final Image pressedImage = new Image(getDisplay(), activeImage, SWT.IMAGE_GRAY);

            final Label clearButton = new Label(parent, SWT.NONE);
            clearButton.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false));
            clearButton.setImage(inactiveImage);
            clearButton.setBackground(parent.getDisplay().getSystemColor(SWT.COLOR_LIST_BACKGROUND));
            clearButton.setToolTipText("Clear");
            clearButton.addMouseListener(new MouseAdapter() {
                private MouseMoveListener fMoveListener;

                @Override
                public void mouseDown(MouseEvent e) {
                    clearButton.setImage(pressedImage);
                    fMoveListener = new MouseMoveListener() {
                        private boolean fMouseInButton = true;

                        public void mouseMove(MouseEvent e) {
                            boolean mouseInButton = isMouseInButton(e);
                            if (mouseInButton != fMouseInButton) {
                                fMouseInButton = mouseInButton;
                                clearButton.setImage(mouseInButton ? pressedImage : inactiveImage);
                            }
                        }
                    };
                    clearButton.addMouseMoveListener(fMoveListener);
                }

                @Override
                public void mouseUp(MouseEvent e) {
                    if (fMoveListener != null) {
                        clearButton.removeMouseMoveListener(fMoveListener);
                        fMoveListener = null;
                        boolean mouseInButton = isMouseInButton(e);
                        clearButton.setImage(mouseInButton ? activeImage : inactiveImage);
                        if (mouseInButton) {
                            clearText();
                            filterText.setFocus();
                        }
                    }
                }

                private boolean isMouseInButton(MouseEvent e) {
                    Point buttonSize = clearButton.getSize();
                    return 0 <= e.x && e.x < buttonSize.x && 0 <= e.y && e.y < buttonSize.y;
                }
            });
            clearButton.addMouseTrackListener(new MouseTrackListener() {
                public void mouseEnter(MouseEvent e) {
                    clearButton.setImage(activeImage);
                }

                public void mouseExit(MouseEvent e) {
                    clearButton.setImage(inactiveImage);
                }

                public void mouseHover(MouseEvent e) {
                }
            });
            clearButton.addDisposeListener(new DisposeListener() {
                public void widgetDisposed(DisposeEvent e) {
                    inactiveImage.dispose();
                    activeImage.dispose();
                    pressedImage.dispose();
                }
            });
            clearButton.getAccessible().addAccessibleListener(new AccessibleAdapter() {
                @Override
                public void getName(AccessibleEvent e) {
                    e.result = "Clear";
                }
            });
            clearButton.getAccessible().addAccessibleControlListener(new AccessibleControlAdapter() {
                @Override
                public void getRole(AccessibleControlEvent e) {
                    e.detail = ACC.ROLE_PUSHBUTTON;
                }
            });
            clearButtonControl = clearButton;
        }
    }

    protected void clearText() {
        setFilterText(""); //$NON-NLS-1$        
        textChanged();
    }

    public void propertyChange(PropertyChangeEvent arg0) {
        updateCloseOnESCMenuState();        
    }

}
